package com.chj.nettydemo.direct;

import cn.hutool.system.SystemUtil;
import com.chj.nettydemo.constant.NettyConst;
import com.chj.nettydemo.direct.channel.Gb32ServerChannelInitializer;
import com.chj.nettydemo.properties.NettyServerProperties;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;

@Slf4j
@Component
public class NettyServer {
    private static final int N_THREADS = Runtime.getRuntime().availableProcessors() * 2;
    private static final String OS_NAME = SystemUtil.getOsInfo().getName().toLowerCase();

    @Resource
    Gb32ServerChannelInitializer gb32ServerChannelInitializer;

    @Resource
    NettyServerProperties nettyServerProperties;

    EventLoopGroup bossGroup;

    EventLoopGroup workGroup;

    @PostConstruct
    public void start() throws InterruptedException {
        bossGroup = !OS_NAME.contains(NettyConst.OS_NAME) ? new EpollEventLoopGroup(1, new DefaultThreadFactory("NettyServer-epollBossEventLoop", true)) : new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServer-nioBossEventLoop", true));
        workGroup = !OS_NAME.contains(NettyConst.OS_NAME) ? new EpollEventLoopGroup(N_THREADS, new DefaultThreadFactory("NettyServer-epollWorkEventLoop", true)) : new NioEventLoopGroup(N_THREADS, new DefaultThreadFactory("NettyServer-nioWorkEventLoop", true));
        ServerBootstrap bootstrap = new ServerBootstrap()
                .group(bossGroup, workGroup)
                .channel(!OS_NAME.contains("windows") ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
                .childHandler(gb32ServerChannelInitializer)
                // 服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
                // backlog 用于构造服务端套接字ServerSocket对象，标识当服务器请求处理线程全满时，用于临时存放已完成三次握手的请求的队列的最大长度。
                .option(ChannelOption.SO_BACKLOG, nettyServerProperties.getSoBacklog())
                // 接收缓冲区大小 64k
                .option(ChannelOption.SO_RCVBUF, 524288)
                // 将小的数据包包装成更大的帧进行传送，提高网络的负载,即TCP延迟传输
                .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE);
        Integer port = nettyServerProperties.getPort();
        ChannelFuture channelFuture = bootstrap.bind(port).sync();
        log.info("NettyServer 服务器启动......");
        channelFuture.addListener(future -> {
            if (future.isSuccess()) {
                log.info("NettyServer 监听{}端口成功", port);
            }
        });
    }

    @PreDestroy
    public void destory() throws InterruptedException {
        bossGroup.shutdownGracefully().sync();
        workGroup.shutdownGracefully().sync();
        log.info("关闭 NettyServer Netty......");
    }
}